"Creating Reusable Animation Components with Next.js and Framer Motion"

"Creating Reusable Animation Components with Next.js and Framer Motion"

info@sanchojralegre.com

Creating Reusable Animation Components with Next.js and Framer Motion

"Over the years of building web applications, I've noticed certain animations that consistently elevate the user experience when implemented well. Smooth entrance animations and dynamic number counting have become hallmarks of modern, polished websites. Rather than recreating these effects from scratch for each project, I've developed a set of reusable components that I now carry into every TypeScript React and Next.js project I build. These components are built with Framer Motion, offering a perfect balance of performance and customization. In this post, I'll share two of my favorite animation components, complete with demos and implementation details. Let's dive into how you can add these engaging animations to your own projects.

Slide-In Animation Component: A Guide to Seamless UI Transitions

Post image

The SlideIn component is a versatile animation wrapper built using Framer Motion that adds smooth entrance animations to any React element. This component particularly shines in creating engaging user interfaces where elements gracefully slide into view as users scroll through your content.

Let's break down the key features:

interface SlideInProps {
  children: React.ReactNode;
  delay?: number;
  duration?: number;
  direction?: "left" | "right" | "up" | "down";
  className?: string;
  amount?: number;
  distance?: number;
  useSpring?: boolean;
}

This interface shows the component's flexibility. You can customize everything from animation timing to direction and even the type of animation (spring or tween).

The heart of the animation logic lies in the direction mapping:


const directionMap = {
  left: { x: -distance, y: 0 },
  right: { x: distance, y: 0 },
  up: { x: 0, y: distance },
  down: { x: 0, y: -distance },
};

This creates smooth transitions from any direction, with the element sliding in from outside the viewport.

Example usage:

<SlideIn
  direction="right"
  delay={0.2}
  duration={0.8}
  useSpring={true}
>
  <div className="p-4 bg-blue-500 text-white">
    This content will slide in from the right
  </div>
</SlideIn>

The component uses Framer Motion's useInView hook to trigger animations when elements enter the viewport:

const isInView = useInView(ref, {
  once: false,
  amount: amount,
});

This creates a scroll-triggered animation effect, where elements animate as they become visible, making your pages feel more dynamic and engaging.

What's particularly powerful is the support for both spring and tween animations through the useSpring prop. Spring animations add a bouncy, playful feel, while tween animations provide a more controlled, linear movement.

The component is fully typed with TypeScript and includes sensible defaults for all properties, making it easy to drop into any React project. Use it for animating hero sections, card reveals, list items, or any element that would benefit from a smooth entrance animation.

"use client";
import React from "react";
import { motion, useInView, useAnimation, type Variants } from "framer-motion";

type SlideDirection = "left" | "right" | "up" | "down";

interface SlideInProps {
  children: React.ReactNode;
  delay?: number;
  duration?: number;
  direction?: SlideDirection;
  className?: string;
  amount?: number;
  distance?: number;
  useSpring?: boolean;
}

const getDirectionalVariants = (
  direction: SlideDirection,
  distance: number,
  duration: number,
  delay: number,
  useSpring: boolean
): Variants => {
  const directionMap = {
    left: { x: -distance, y: 0 },
    right: { x: distance, y: 0 },
    up: { x: 0, y: distance },
    down: { x: 0, y: -distance },
  };

  return {
    hidden: {
      ...directionMap[direction],
      opacity: 0,
    },
    visible: {
      x: 0,
      y: 0,
      opacity: 1,
      transition: useSpring
        ? {
            type: "spring",
            bounce: 0.3,
            duration: duration,
            delay: delay,
          }
        : {
            type: "tween",
            ease: "easeOut",
            duration: duration,
            delay: delay,
          },
    },
  };
};

export const SlideIn: React.FC<SlideInProps> = ({
  children,
  delay = 0,
  duration = 0.5,
  direction = "left",
  className = "",
  amount = 0.3,
  distance = 100,
  useSpring = false,
}) => {
  const controls = useAnimation();
  const ref = React.useRef(null);
  const isInView = useInView(ref, {
    once: false,
    amount: amount,
  });

  const variants = React.useMemo(
    () =>
      getDirectionalVariants(direction, distance, duration, delay, useSpring),
    [direction, distance, duration, delay, useSpring]
  );

  React.useEffect(() => {
    if (isInView) {
      controls.start("visible");
    } else {
      controls.start("hidden");
    }
  }, [controls, isInView]);

  return (
    <motion.div
      ref={ref}
      initial="hidden"
      animate={controls}
      variants={variants}
      className={className}
    >
      {children}
    </motion.div>
  );
};


CountUp Animation Component: Dynamic Number Animations Made Simple

Post image

The CountUp component is a lightweight, yet powerful number animation component built with Framer Motion. It creates smooth, engaging animations that count up to a target number when the element enters the viewport, perfect for statistics, metrics, or any numerical data you want to highlight.

Let's examine the key features:

interface CountUpProps {
  target: number;
  duration?: number;
  delay?: number;
  className?: string;
  suffix?: string;
  prefix?: string;
}

The component accepts a target number and optional formatting props, including duration controls and the ability to add prefixes (like '$') or suffixes (like '%').

The magic happens in the animation setup:

const controls = animate(count, target, {
  duration: duration,
  delay: delay,
  ease: "easeOut",
  onUpdate: (latest) => {
    setDisplayValue(Math.round(latest));
  },
});

This creates a smooth animation from 0 to your target number, with the current value updating in real-time.

Example usage:

<CountUp
  target={1000}
  duration={2.5}
  delay={0.2}
  prefix="$"
  suffix="+"
/>

The component uses Framer Motion's useInView hook to trigger animations when scrolling:

const isInView = useInView(ref, { once: false });

This means the animation restarts each time the element comes into view, creating an engaging scroll experience.

What makes this component particularly useful is its combination of simplicity and flexibility. It's perfect for:

  • Displaying statistics

  • Showing progress metrics

  • Animating pricing numbers

  • Visualizing achievement counts

  • Highlighting any numerical data

The component is fully typed with TypeScript and includes sensible defaults, making it a drop-in solution for any React project that needs engaging number animations.

import { animate, useInView, useMotionValue } from "framer-motion";
import React from "react";

interface CountUpProps {
  target: number;
  duration?: number;
  delay?: number;
  className?: string;
  suffix?: string;
  prefix?: string;
}

export const CountUp: React.FC<CountUpProps> = ({
  target,
  duration = 2,
  delay = 0,
  className = "",
  suffix = "",
  prefix = "",
}) => {
  const ref = React.useRef(null);
  const isInView = useInView(ref, { once: false });
  const count = useMotionValue(0);
  const [displayValue, setDisplayValue] = React.useState(0);

  React.useEffect(() => {
    if (isInView) {
      const controls = animate(count, target, {
        duration: duration,
        delay: delay,
        ease: "easeOut",
        onUpdate: (latest) => {
          setDisplayValue(Math.round(latest));
        },
        onComplete: () => {
          setDisplayValue(target);
        },
      });

      return controls.stop;
    } else {
      setDisplayValue(0);
    }
  }, [count, target, duration, delay, isInView]);

  return (
    <span ref={ref} className={className}>
      {prefix}
      {displayValue}
      {suffix}
    </span>
  );
};

For more tips and components, sign up for our newsletter!

    Float Infinity Logo

    Powered by Float Infinity

    Follow Us

    Privacy PolicyTerms of Service© 2024 Float Infinity